/*
 * Copyright (c) 2020-2020, org.smartboot. All rights reserved.
 * project name: smart-license
 * file name: LicenseServer.java
 * Date: 2020-03-22
 * Author: sandao (zhengjunweimail@163.com)
 */

package org.smartboot.license.server;


import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.io.IOUtils;
import org.smartboot.license.client.LicenseEntity;
import org.smartboot.license.client.Md5;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Calendar;
import java.util.Date;
import java.util.Properties;

/**
 * @author 三刀
 * @version V1.0 , 2020/3/20
 */
public class LicenseServer {
    private static final String SOURCE_FILE = "source.txt";
    private static final String LICENSE_FILE = "license.txt";
    private static final String COMMAND_EXPIRE = "e";
    private static final String COMMAND_FILE = "f";
    private static final String COMMAND_TEXT = "t";
    private static final String COMMAND_PRIVATE_KEY = "k";

    private final File sourceFile;
    private final File licenseFile;

    public LicenseServer(File sourceFile, File licenseFile) {
        this.sourceFile = sourceFile;
        this.licenseFile = licenseFile;
    }

    public static void main(String[] args) throws Exception {
//        System.out.println(Arrays.toString(args));
        CommandLine commandLine = parseCli(args);
        if (commandLine == null) {
            return;
        }

        LicenseServer license = new LicenseServer(new File(SOURCE_FILE), new File(LICENSE_FILE));
        String expire = commandLine.getOptionValue(COMMAND_EXPIRE);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Date expireDate = sdf.parse(expire);
        String filePath = commandLine.getOptionValue(COMMAND_FILE);
        String text = commandLine.getOptionValue(COMMAND_TEXT);
        String key = commandLine.getOptionValue(COMMAND_PRIVATE_KEY);
        byte[] keyBytes = null;
        if (key != null) {
            try {
                keyBytes = Base64.getDecoder().decode(key);
            } catch (Exception e) {
                System.out.println("PrivateKey decode fail: " + e.getMessage());
                return;
            }
        }

        byte[] bytes;
        if (filePath != null) {
            File file = new File(filePath);
            if (!file.isFile()) {
                System.out.println("none file:" + file.getAbsolutePath());
                return;
            }
            System.out.println("sign for file:" + file.getPath());
            bytes = IOUtils.toByteArray(new FileInputStream(file));
        } else {
            if (text == null) {
                System.out.println("none text");
                return;
            }
            System.out.println("sign for string:" + text);
            bytes = text.getBytes();
        }
        if (keyBytes != null) {
            license.createLicense(bytes, expireDate, keyBytes);
        } else {
            license.createLicense(bytes, expireDate);
        }
    }

    private static CommandLine parseCli(String[] args) throws ParseException {
        Options options = new Options();
        options.addOption(COMMAND_FILE, "file", true, "file to be signed.");
        options.addOption(COMMAND_TEXT, "text", true, "text to be signed.");
        options.addOption(COMMAND_PRIVATE_KEY, "key", true, "encrypted private key, generated by keypair.");
        options.addOption(COMMAND_EXPIRE, "expire", true, "License expiration time, format: yyyy-MM-dd");
        CommandLineParser parser = new DefaultParser();
        CommandLine commandLine = parser.parse(options, args);
        if (commandLine.hasOption(COMMAND_FILE) && commandLine.hasOption(COMMAND_TEXT)) {
            HelpFormatter formatter = new HelpFormatter();
            formatter.printHelp("-f and -t cannot exist at the same time", options);
            return null;
        }
        if (!commandLine.hasOption(COMMAND_FILE) && !commandLine.hasOption(COMMAND_TEXT)) {
            HelpFormatter formatter = new HelpFormatter();
            formatter.printHelp("must specify -f or -t", options);
            return null;
        }
        if (!commandLine.hasOption(COMMAND_EXPIRE)) {
            HelpFormatter formatter = new HelpFormatter();
            formatter.printHelp("must specify -e", options);
            return null;
        }
        return commandLine;
    }

    /**
     * 采用非对称加密对data作预处理
     */
    public void createLicense(byte[] data, Date expireDate, byte[] privateKey) throws Exception {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        int offset = 0;
        int step = 64;
        if (step > data.length) {
            step = data.length;
        }
        while (offset < data.length) {
            byte[] encryptData = RasUtil.encryptByPrivateKey(data, privateKey, offset, step);
            byteArrayOutputStream.write(encryptData.length);
            byteArrayOutputStream.write(encryptData);
            offset += step;
            step = Math.min(data.length - offset, step);
        }
        createLicense(byteArrayOutputStream.toByteArray(), expireDate);
    }

    /**
     * 生成License
     *
     * @param data       license内容
     * @param expireDate 过期时间
     */
    public void createLicense(byte[] data, Date expireDate) throws Exception {
        if (expireDate == null) {
            Calendar calendar = Calendar.getInstance();
            calendar.add(Calendar.YEAR, 100);
            expireDate = calendar.getTime();
        }
        //初始化密钥
        //生成密钥对
        KeyPair keyPair = RasUtil.initKey();

        LicenseEntity entity = new LicenseEntity(expireDate.getTime(), RasUtil.getPublicKey(keyPair), Md5.md5(data));
        entity.setData(data);
        createLicense(entity, keyPair);

        createSourceLicense(entity, keyPair);
    }

    private void createLicense(LicenseEntity entity, KeyPair keyPair) throws Exception {
        //生成License
        try (FileOutputStream fileOutputStream = new FileOutputStream(licenseFile)) {
            LicenseEncode licenseEncode = new LicenseEncode();
            fileOutputStream.write(Base64.getEncoder().encodeToString(licenseEncode.encode(entity, RasUtil.getPrivateKey(keyPair))).getBytes(StandardCharsets.UTF_8));
        }
    }

    /**
     * 生成License源文件
     */
    private void createSourceLicense(LicenseEntity entity, KeyPair keyPair) throws IOException {
        SourceLicense sourceLicense = new SourceLicense(entity, keyPair);
        //生成License
        try (FileWriter fileWriter = new FileWriter(sourceFile)) {
            Properties properties = new Properties();
            properties.setProperty(SourceLicense.PROPERTY_APPLY_DATE, sourceLicense.getApplyDate());
            properties.setProperty(SourceLicense.PROPERTY_EXPIRE_DATE, sourceLicense.getExpireDate());
            properties.setProperty(SourceLicense.PROPERTY_BASE64_CONTENT, sourceLicense.getBase64Content());
            properties.setProperty(SourceLicense.PROPERTY_PUBLIC_KEY, sourceLicense.getPublicKey());
            properties.put(SourceLicense.PROPERTY_PRIVATE_KEY, sourceLicense.getPrivateKey());
            properties.store(fileWriter, null);
        }
        System.out.println("Expire Date:" + sourceLicense.getExpireDate());
    }
}
